package oen.game.csv.loader;

import im.oen.boot.common.utils.*;
import oen.game.csv.loader.data.FieldData;
import oen.game.csv.loader.util.FieldValueConvert;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;

import javax.naming.NoPermissionException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>描述：CSV配置文件管理器</p>
 *
 * <p>创建时间：2021-11-09 12:36</p>
 * <p>更新时间：暂无</p>
 *
 * @author Kevin.Xu
 * @version 1.0
 */
public class CSVManager<T extends Serializable> {

    private Class<T> clazz;

    private List<T> OBJ_LIST = new ArrayList<>();
    private Map<Object, T> ID_OBJ = new ConcurrentHashMap<>();
    private Map<Object, Set<T>> PARENT_ID_CHILD = new ConcurrentHashMap<>();
    private Map<Object, Set<T>> GROUP_ID_LIST = new ConcurrentHashMap<>();

    private CSVManager(InputStream inputStream, Class<T> clazz) throws Exception {
        this.clazz = clazz;
        this.init(inputStream);
    }

    private void init(InputStream inputStream) throws Exception {
        Iterator<CSVRecord> record = CSVFormat.EXCEL.parse(new InputStreamReader(inputStream)).stream().iterator();
        if (!record.hasNext()) {
            throw new NullPointerException("CSV can not be empty");
        }

        List<Field> fields = ReflectUtil.fetchFields(clazz);
        Map<Field, FieldData> heads = CSVLoader.readHead(fields, record.next());

        while (record.hasNext()) {
            final CSVRecord data = record.next();
            boolean emptyObject = true;
            Object id = null;
            T obj = ReflectUtil.newObj(clazz);
            for (Field field : fields) {
                final FieldData fieldData = heads.get(field);
                if (fieldData == null) {
                    continue;
                }

                String dataValue = fieldData.getDefaultValue();
                if (fieldData.getIdx() >= 0) {
                    dataValue = Runner.safeRun(() -> {
                        String v = data.get(fieldData.getIdx());
                        return Checker.isEmpty(v) ? null : v;
                    }, dataValue);
                }
                Object v = FieldValueConvert.to(dataValue, fieldData);
                if (v != null) {
                    ReflectUtil.setFieldValue(obj, field, v);
                    emptyObject = false;
                    if (fieldData.isId()) {
                        id = v;
                    }
                }
            }

            if (!emptyObject) {
                OBJ_LIST.add(obj);
                if (id != null) {
                    ID_OBJ.put(id, obj);
                }
            }
        }

        if (Checker.isEmpty(OBJ_LIST)) {
            return;
        }

        FieldData parent = null, group = null;
        for (FieldData data : heads.values()) {
            if (data.isParentId()) {
                parent = data;
            }
            if (data.isGroupId()) {
                group = data;
            }
        }
        for (T d : OBJ_LIST) {
            if (group != null) {
                Object value = ReflectUtil.fetchValue(d, group.getField());
                if (Checker.isNotEmpty(value)) {
                    Set<T> sets = GROUP_ID_LIST.get(value);
                    if (sets == null) {
                        sets = new HashSet<>();
                    }
                    sets.add(d);
                    GROUP_ID_LIST.put(value, sets);
                }
            }

            if (parent != null) {
                Object value = ReflectUtil.fetchValue(d, parent.getField());
                if (Checker.isNotEmpty(value)) {
                    Set<T> sets = PARENT_ID_CHILD.get(value);
                    if (sets == null) {
                        sets = new HashSet<>();
                    }
                    sets.add(d);
                    PARENT_ID_CHILD.put(value, sets);
                }
            }
        }
    }

    /**
     * @param id 注意参数类型，要和字段类型一致
     * @return 对象
     */
    public T fetchById(Serializable id) {
        return ID_OBJ.get(id);
    }

    /**
     * @param parentId 注意参数类型，要和字段类型一致
     * @return 对象
     */
    public Set<T> fetchChildByParentId(Serializable parentId) {
        return PARENT_ID_CHILD.get(parentId);
    }

    /**
     * @param groupId 注意参数类型，要和字段类型一致
     * @return 对象
     */
    public Set<T> fetchGroupByGroupId(Serializable groupId) {
        return GROUP_ID_LIST.get(groupId);
    }

    /**
     * 获得文件所有的信息
     *
     * @return 对象
     */
    public List<T> all() {
        return OBJ_LIST;
    }

    /**
     * @param id 注意参数类型，要和字段类型一致
     * @return 对象
     */
    public T copyById(Serializable id) {
        T data = ID_OBJ.get(id);
        if (data == null) {
            return null;
        }
        return SerializeUtil.bit2Obj(SerializeUtil.obj2Bit(data), clazz);
    }

    /**
     * @param parentId 注意参数类型，要和字段类型一致
     * @return 对象
     */
    public Set<T> copyChildByParentId(Serializable parentId) {
        Set<T> sets = PARENT_ID_CHILD.get(parentId);
        if (Checker.isEmpty(sets)) {
            return null;
        }
        return JsonUtil.safeToSet(JsonUtil.safeToJson(sets), clazz);
    }

    /**
     * @param groupId 注意参数类型，要和字段类型一致
     * @return 对象
     */
    public Set<T> copyGroupByGroupId(Serializable groupId) {
        Set<T> sets = GROUP_ID_LIST.get(groupId);
        if (Checker.isEmpty(sets)) {
            return null;
        }
        return JsonUtil.safeToSet(JsonUtil.safeToJson(sets), clazz);
    }

    public List<T> copyAll() {
        return JsonUtil.safeToList(JsonUtil.safeToJson(OBJ_LIST), clazz);
    }


    public static <T extends Serializable> CSVManager<T> of(String path, Class<T> clazz) throws Exception {
        File file = new File(path);
        if (!file.exists()) {
            throw new FileNotFoundException(path);
        }
        if (!file.canRead()) {
            throw new NoPermissionException();
        }

        return new CSVManager<>(new FileInputStream(file), clazz);
    }

    public static <T extends Serializable> CSVManager<T> of(InputStream inputStream, Class<T> clazz) throws Exception {
        if (inputStream == null) {
            throw new FileNotFoundException();
        }

        return new CSVManager<>(inputStream, clazz);
    }
}
